
/* GCSx
** LIST.CPP
**
** Listbox, drop-down, etc. dialog widgets
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

// Listbox entries
ListEntry& ListEntry::operator=(const ListEntry& right) { start_func
    label = right.label;
    selected = right.selected;
    disabled = right.disabled;
    id = right.id;
    code1 = right.code1;
    code2 = right.code2;
    code3 = right.code3;
    return *this;
}

ListEntry::ListEntry() { start_func
    label = blankString;
    selected = disabled = 0;
    id = 0;
    code1 = code2 = code3 = 0;
}

ListEntry::ListEntry(const ListEntry& from) { start_func
    label = from.label;
    selected = from.selected;
    disabled = from.disabled;
    id = from.id;
    code1 = from.code1;
    code2 = from.code2;
    code3 = from.code3;
}

ListEntry::ListEntry(const string& eLabel, int eId, int eCode1, int eCode2, int eCode3, int eDisabled) { start_func
    label = eLabel;
    selected = 0;
    disabled = eDisabled;
    id = eId;
    code1 = eCode1;
    code2 = eCode2;
    code3 = eCode3;
}

// Listbox widget

WListBox::WListBox(int lId, int* lSelection, int lAllowZero, int lWidth, int lLines) : Widget(lId, blankString, lSelection), contents() { start_func
    assert(lSelection);

    wparent = NULL;
    valueHeight = fontHeight();
    valueWidth = 0;
    numValues = 0;
    cursorPos = 0;
    selectedItem = SELECTED_NONE;
    allowZero = lAllowZero;
    allowMany = 0;
    
    // Set to standard size
    if (lWidth > 0) {
        showWidth = lWidth;
    }
    else {
        string standard(GUI_LISTBOX_DEFAULTWIDTH, 'X');
        showWidth = GUI_LISTBOX_HORIZPAD * 2 + fontWidth(standard);
    }

    showLines = lLines < 1 ? GUI_LISTBOX_DEFAULTLINES : lLines;
    
    resize(0, 0);
}

// Needed due to virtual ~Widget() and our list members
WListBox::~WListBox() { start_func
}

void WListBox::moveCursor(int id) { start_func
    // Clip position
    if (id < 0) id = 0;
    if (id >= numValues) id = numValues - 1;
    if (cursorPos == id) return;
    
    cursorPos = id;
    setDirty();
    if (wparent) wparent->scrollToView(0, cursorPos * valueHeight, width, valueHeight);
}

void WListBox::selectItem(int id, int select, int alone) { start_func
    assert(id >= 0);
    assert(id < numValues);
    
    int dirty = 0;

    // (if disabled, no effect at all)
    if (contents[id].disabled) return;
    
    // Force 'alone' if only one allowed to be selected
    if ((select) && (!allowMany)) alone = 1;
    
    // (manually deselect all so we don't send dirty/modified yet)
    // 'alone' has no meaning if deselecting
    if ((alone) && (select)) {
        if (selectedItem == SELECTED_MANY) {
            for (int pos = 0; pos < numValues; ++pos) {
                contents[pos].selected = 0;
            }
            dirty = 1;
        }
        else if (selectedItem != SELECTED_NONE) {
            contents[selectedItem].selected = 0;
            dirty = 1;
        }
        selectedItem = SELECTED_NONE;
    }
    
    if (select) {
        if (selectedItem == SELECTED_NONE) selectedItem = id;
        else selectedItem = SELECTED_MANY;
        contents[id].selected = 1;
        dirty = 1;
    }
    else if (contents[id].selected) {
        // Disallow deselection if not allowed to select zero
        if ((!allowZero) && (selectedItem == id)) return;
        
        contents[id].selected = 0;
        if (selectedItem == id) selectedItem = SELECTED_NONE;
        else {
            // Count how many are selected now
            selectedItem = SELECTED_NONE;
            for (int pos = 0; pos < numValues; ++pos) {
                if (contents[pos].selected) {
                    if (selectedItem == SELECTED_NONE) selectedItem = pos;
                    else {
                        selectedItem = SELECTED_MANY;
                        break;
                    }
                }
            }
        }
        dirty = 1;
    }

    if (dirty) {
        // @TODO: 3 exceptions
        if (wparent) wparent->myParent()->childModified(this);
        setDirty();
    }
}

int WListBox::event(int hasFocus, const SDL_Event* event) { start_func
    assert(event);
    assert(parent);
    
    if ((numValues == 0) && (event->type != SDL_INPUTFOCUS)) return 0;

    switch (event->type) {
        case SDL_INPUTFOCUS:
            if (event->user.code & 1) {
                if (!haveFocus) {
                    haveFocus = 1;
                    setDirty();
                }
            }
            else {
                if (haveFocus) {
                    haveFocus = 0;
                    setDirty();
                }
            }
            return 1;
            
        case SDL_KEYDOWN:
            // @TODO: Ctrl should move cursor without selecting or select multiple
            //        Shift should select a range

            switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
                case SDLK_SPACE:
                    selectItem(cursorPos, 0);
                    return 1;

                case SDLK_DOWN:
                case SDLK_RIGHT:
                    // Next line
                    moveCursor(cursorPos + 1);
                    selectItem(cursorPos);
                    return 1;
                        
                case SDLK_UP:
                case SDLK_LEFT:
                    // Previous line
                    moveCursor(cursorPos - 1);
                    selectItem(cursorPos);
                    return 1;

                case SDLK_PAGEDOWN:
                    // Next page
                    moveCursor(cursorPos + showLines - 1);
                    selectItem(cursorPos);
                    return 1;

                case SDLK_PAGEUP:
                    // Previous page
                    moveCursor(cursorPos - showLines + 1);
                    selectItem(cursorPos);
                    return 1;

                case SDLK_HOME:
                    // First item
                    moveCursor(0);
                    selectItem(cursorPos);
                    return 1;

                case SDLK_END:
                    // Last item
                    moveCursor(numValues - 1);
                    selectItem(cursorPos);
                    return 1;
                    
                default: {
                    // Jump to first item beginning with a letter
                    int key = tolower(event->key.keysym.sym);
                    for (int pos = 0; pos < numValues; ++pos) {
                        if (tolower(contents[pos].label[0]) == key) {
                            moveCursor(pos);
                            selectItem(cursorPos);
                            return 1;
                        }
                    }
                    break;
                }
            }
            break;
            
        case SDL_MOUSEBUTTONDBL:
        case SDL_MOUSEBUTTONDOWN:
            if (event->button.button == SDL_BUTTON_LEFT) {
                dragSet = 1;
            
                if (event->button.y < height) {
                    int targetPos = event->button.y / valueHeight;

                    // If CTRL and was already selected, just unselect
                    if ((contents[targetPos].selected) && (SDL_GetModState() & KMOD_CTRL) && (allowZero)) {
                        dragSet = 0;
                    }

                    moveCursor(targetPos);
                    selectItem(cursorPos, dragSet);

                    // Double-click?
                    if (event->type == SDL_MOUSEBUTTONDBL) {
                        // (find OK button)
                        if (wparent->myParent()->buttonControl(Dialog::BUTTON_OK)) {
                            wparent->myParent()->currentFocusDoAction();
                        }
                    }

                    return 1;
                }
            }
            break;

        case SDL_MOUSEMOTION:
            if (event->motion.state & SDL_BUTTON_LMASK) {
                if (event->motion.y < height) {
                    moveCursor(event->motion.y / valueHeight);
                    // @TODO: Should select/deselect all in between
                    selectItem(cursorPos, dragSet);
                    return 1;
                }
            }
            break;
    }

    return 0;
}

void WListBox::load() { start_func
    int sel = *(int*)(setting);
    int count = 0;
    cursorPos = 0;
    selectedItem = SELECTED_NONE;
    
    for (int pos = 0; pos < numValues; ++pos) {
        if (contents[pos].id == sel) {
            if (!count) {
                contents[pos].selected = 1;
                selectedItem = cursorPos = pos;
            }
            ++count;
        }
        else contents[pos].selected = 0;
    }
    
    if ((!count) && (!allowZero) && (numValues)) {
        contents[0].selected = 1;
        selectedItem = cursorPos = 0;
    }
}

void WListBox::apply() { start_func
    *(int*)(setting) = 0;
    
    // @TODO: Support for multiple selections
    if (selectedItem >= 0) {
        *(int*)(setting) = contents[selectedItem].id;
    }
}

void WListBox::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // If dirty, redraw all
        if (dirty) {
            getRect(toDisplay);
            toDisplay.x += xOffset;
            toDisplay.y += yOffset;
            // Always fill entire width of view
            if (viewWidth > toDisplay.w) toDisplay.w = viewWidth;
            dirty = 0;
            intersectRects(toDisplay, clipArea);
        }
        
        // Anything to draw?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);
        
            xOffset += x;
            yOffset += y;

            // This widget attempts to only redraw listbox entries within the
            // dirtied area.

            // Background
            SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_TEXTBOX]);
            
            // Draw all listbox entries we can see
            int pos = (toDisplay.y - yOffset) / valueHeight;
            // (add in value height - 1 so it rounds up)
            int last = (toDisplay.y + toDisplay.h - yOffset + valueHeight - 1) / valueHeight;
            if (last > numValues) last = numValues;
            
            int shownWidth = valueWidth + GUI_LISTBOX_HORIZPAD * 2;
            if (viewWidth > shownWidth) shownWidth = viewWidth;
            
            for (; pos < last; ++pos) {
                if ((contents[pos].selected) && (!disabled) && (!contents[pos].disabled)) {
                    drawGradient(xOffset, valueHeight * pos + yOffset,
                             shownWidth, valueHeight, guiRGB[COLOR_SELECTION1],
                             guiRGB[COLOR_SELECTION2], destSurface);
                    drawText(contents[pos].label, guiRGB[COLOR_TEXTBOX],
                             xOffset + GUI_LISTBOX_HORIZPAD, valueHeight * pos + yOffset, destSurface);
                }
                else {
                    drawText(contents[pos].label, guiRGB[(disabled || contents[pos].disabled) ? COLOR_LIGHT2 : COLOR_TEXT],
                             xOffset + GUI_LISTBOX_HORIZPAD, valueHeight * pos + yOffset, destSurface);
                }
            
                if ((haveFocus) && (pos == cursorPos)) {
                    drawFocusBox(xOffset, valueHeight * pos + yOffset,
                                 shownWidth, valueHeight, destSurface);
                }
            }
        }
    }
}

void WListBox::addTo(Dialog* dialog) { start_func
    wparent = new WidgetScroll(WidgetScroll::FRAMETYPE_BEVEL, this, showWidth, showLines * valueHeight);
    dialog->addWidget(wparent);
}

void WListBox::clear() { start_func
    contents.clear();
    numValues = 0;
    valueWidth = 0;
    cursorPos = 0;
    selectedItem = SELECTED_NONE;
    resize(0, 0);
}

void WListBox::sortItems() { start_func
    sort(contents.begin(), contents.end());
    setDirty();

    // Find which item is selected now
    selectedItem = SELECTED_NONE;
    for (int pos = 0; pos < numValues; ++pos) {
        if (contents[pos].selected) {
            if (selectedItem == SELECTED_NONE) selectedItem = pos;
            else {
                selectedItem = SELECTED_MANY;
                break;
            }
        }
    }
}

void WListBox::modifyItem(int position, const std::string* newLabel, const int* newDisabled, const int* newCode1, const int* newCode2, const int* newCode3) { start_func
    if (newCode1) contents[position].code1 = *newCode1;
    if (newCode2) contents[position].code2 = *newCode2;
    if (newCode3) contents[position].code3 = *newCode3;
    if (newDisabled) {
        // If newly disabled, deselecte
        if ((*newDisabled) && (!contents[position].disabled) && (contents[id].selected)) {
            if ((!allowZero) && (selectedItem == position)) {
                // @TODO: If only item selected and can't select zero items
            }
            else {
                // Just deselect
                selectItem(position, 0, 0);
            }
        }
        contents[position].disabled = *newDisabled;
    }

    // Name change- update width
    if (newLabel) {
        contents[position].label = *newLabel;
        // (limit length of display but don't affect actual data)
        int itemWidth = fontWidth(newLabel->substr(0, MAX_LINELENGTH));
        if (itemWidth > valueWidth) {
            valueWidth = itemWidth;
            resize(valueWidth + GUI_LISTBOX_HORIZPAD * 2, valueHeight * numValues);
        }
    }
    
    // Update display
    setDirty();
}

void WListBox::addItem(const ListEntry& item, int selectIt) { start_func
    contents.push_back(item);

    numValues = contents.size();
    // (limit length of display but don't affect actual data)
    int itemWidth = fontWidth(item.label.substr(0, MAX_LINELENGTH));
    if (itemWidth > valueWidth) valueWidth = itemWidth;
    
    if (selectIt) {
        moveCursor(numValues - 1);
        selectItem(cursorPos);
    }

    // Dirties (since we are definately larger now)
    resize(valueWidth + GUI_LISTBOX_HORIZPAD * 2, valueHeight * numValues);
}

int WListBox::refuseAll() const { start_func
    if ((numValues == 0) || (disabled)) return 1;
    return 0;
}

const ListEntry* WListBox::findEntry(int id) const { start_func
    for (int pos = 0; pos < numValues; ++pos) {
        if (contents[pos].id == id) {
            return &contents[pos];
        }
    }
    return NULL;
}


